package goreport

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"runtime"
)

// Call frame data.
type frame struct {
	// Program location counter.
	Counter uintptr
	// File name.
	File,
	// Function name with package path.
	Function string
	// Line number in the file.
	Line uint
}

// Return saved call frames.
func (r Report) Frames() []frame {
	r.frames = append([]frame{}, r.frames...)

	return r.frames
}

// Return frames after skipping skip calls.
func (r Report) Save(skip int) (Report, error) {
	var framesTmp []frame

	for frameNum := 0; frameNum <= skip; frameNum++ {
		var (
			line int
			ok   bool
		)

		framesTmp = append(framesTmp, frame{})

		if framesTmp[frameNum].Counter, framesTmp[frameNum].File, line, ok = runtime.Caller(frameNum); !ok {
			return r, fmt.Errorf("failed to get call of depth %d", frameNum)
		}

		framesTmp[frameNum].Line = uint(line)

		framesTmp[frameNum].Function = runtime.FuncForPC(framesTmp[frameNum].Counter).Name()
	}

	r.frames = framesTmp

	return r, nil
}

// Return the string f.Line from f.File along the boundaries of up and down.
func (f frame) Source(up, down uint) (lines []string, err error) {
	var (
		fd      *os.File
		lineNum uint
	)

	if f.Line == 0 {
		return lines, errors.New("line number cannot be zero")
	}

	if fd, err = os.Open(f.File); err != nil {
		return
	}
	defer fd.Close()

	fdScanner := bufio.NewScanner(fd)

	for lineNum = 1; fdScanner.Scan(); lineNum++ {
		if lineNum < f.Line-up {
			continue
		} else if f.Line+down < lineNum {
			return
		}

		lines = append(lines, string(fdScanner.Bytes()))
	}

	return lines, fdScanner.Err()
}
